Skip to content

SONARJAVA-6706 Implement new rule S2330 - #5867

Draft
romainbrenguier wants to merge 6 commits into
masterfrom
new-rule/SONARJAVA-6706-S2330
Draft

SONARJAVA-6706 Implement new rule S2330#5867
romainbrenguier wants to merge 6 commits into
masterfrom
new-rule/SONARJAVA-6706-S2330

Conversation

@romainbrenguier

Copy link
Copy Markdown
Contributor

Detect array covariance where an array of a derived type is assigned to a variable declared as an array of its base type, which can lead to ArrayStoreException at runtime.

@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

SONARJAVA-6706

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #5868

Please review and merge it into your branch.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #5868

Please review and merge it into your branch.

romainbrenguier pushed a commit that referenced this pull request Aug 4, 2026
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Ruling Diff Summary

Detected changes in 5 rule files: 0 issues removed, 161 issues added.

S2330 (java) on commons-beanutils - 0 issues removed, 5 issues added - new ruling file

Added src/main/java/org/apache/commons/beanutils2/LazyDynaList.java (line 418)

       413 |      */
       414 |     @Override
       415 |     public Object[] toArray() {
       416 | 
       417 |         if (size() == 0 && elementType == null) {
>>>    418 |             return new LazyDynaBean[0];
       419 |         }
       420 | 
       421 |         final Object[] array = (Object[])Array.newInstance(elementType, size());
       422 |         for (int i = 0; i < size(); i++) {
       423 |             if (Map.class.isAssignableFrom(elementType)) {

Added src/main/java/org/apache/commons/beanutils2/LazyDynaList.java (line 494)

       489 |      * @return A DynaBean[] of the elements in this List.
       490 |      */
       491 |     public DynaBean[] toDynaBeanArray() {
       492 | 
       493 |         if (size() == 0 && elementDynaBeanType == null) {
>>>    494 |             return new LazyDynaBean[0];
       495 |         }
       496 | 
       497 |         final DynaBean[] array = (DynaBean[])Array.newInstance(elementDynaBeanType, size());
       498 |         for (int i = 0; i < size(); i++) {
       499 |             array[i] = (DynaBean)get(i);

Added src/main/java/org/apache/commons/beanutils2/MethodUtils.java (line 1352)

      1347 |             final MethodDescriptor md = (MethodDescriptor)obj;
      1348 | 
      1349 |             return exact == md.exact &&
      1350 |             methodName.equals(md.methodName) &&
      1351 |             cls.equals(md.cls) &&
>>>   1352 |             java.util.Arrays.equals(paramTypes, md.paramTypes);
      1353 |         }
      1354 |         /**
      1355 |          * Returns the string length of method name. I.e. if the
      1356 |          * hashcodes are different, the objects are different. If the
      1357 |          * hashcodes are the same, need to use the equals method to

Added src/test/java/org/apache/commons/beanutils2/LazyDynaListTestCase.java (line 209)

       204 |         }
       205 | 
       206 | 
       207 | 
       208 |         // ----- Create LazyArrayList from Array -----
>>>    209 |         lazyList = new LazyDynaList(testArray);
       210 |         assertEquals("6. check size", size, lazyList.size());
       211 | 
       212 |         dynaArray = lazyList.toDynaBeanArray();
       213 |         mapArray  = (TreeMap[])lazyList.toArray();
       214 | 
S2330 (java) on eclipse-jetty - 0 issues removed, 40 issues added - new ruling file

Added jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java (line 277)

(source file not found at this revision: jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java)

Added jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java (line 249)

(source file not found at this revision: jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/ClassLoaderDump.java (line 54)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/ClassLoaderDump.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/Server.java (line 274)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/Server.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java (line 67)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java (line 125)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java (line 847)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java (line 869)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java)

Added jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java (line 603)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerTest.java)

Added jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java (line 147)

(source file not found at this revision: jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java)

Added jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java (line 168)

(source file not found at this revision: jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java)

Added jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java (line 170)

(source file not found at this revision: jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java)

Added jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java (line 141)

(source file not found at this revision: jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java)

Added jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java (line 143)

(source file not found at this revision: jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java)

Added jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java (line 83)

(source file not found at this revision: jetty-util/src/main/java/org/eclipse/jetty/util/TreeTrie.java)
S2330 (java) on eclipse-jetty-similar-to-main - 0 issues removed, 10 issues added - new ruling file

Added jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java (line 277)

(source file not found at this revision: jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java)

Added jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java (line 249)

(source file not found at this revision: jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/ClassLoaderDump.java (line 54)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/ClassLoaderDump.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/Server.java (line 274)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/Server.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java (line 67)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java (line 125)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java (line 847)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java)

Added jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java (line 869)

(source file not found at this revision: jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java)
S2330 (java) on guava - 0 issues removed, 43 issues added - new ruling file

Added src/com/google/common/collect/ArrayTable.java (line 385)

       380 |    * Associates the value {@code null} with every pair of allowed row and column
       381 |    * keys.
       382 |    */
       383 |   public void eraseAll() {
       384 |     for (V[] row : array) {
>>>    385 |       Arrays.fill(row, null);
       386 |     }
       387 |   }
       388 | 
       389 |   /**
       390 |    * Returns {@code true} if the provided keys are among the keys provided when

Added src/com/google/common/collect/HashBiMap.java (line 378)

       373 |   }
       374 | 
       375 |   @Override
       376 |   public void clear() {
       377 |     size = 0;
>>>    378 |     Arrays.fill(hashTableKToV, null);
       379 |     Arrays.fill(hashTableVToK, null);
       380 |     firstInKeyInsertionOrder = null;
       381 |     lastInKeyInsertionOrder = null;
       382 |     modCount++;
       383 |   }

Added src/com/google/common/collect/HashBiMap.java (line 379)

       374 | 
       375 |   @Override
       376 |   public void clear() {
       377 |     size = 0;
       378 |     Arrays.fill(hashTableKToV, null);
>>>    379 |     Arrays.fill(hashTableVToK, null);
       380 |     firstInKeyInsertionOrder = null;
       381 |     lastInKeyInsertionOrder = null;
       382 |     modCount++;
       383 |   }
       384 | 

Added src/com/google/common/collect/ImmutableBiMap.java (line 228)

       223 |             }
       224 |             Arrays.sort(entries, 0, size,
       225 |                 Ordering.from(valueComparator).onResultOf(Maps.<V>valueFunction()));
       226 |           }
       227 |           entriesUsed = size == entries.length;
>>>    228 |           return RegularImmutableBiMap.fromEntryArray(size, entries);
       229 |       }
       230 |     }
       231 |   }
       232 | 
       233 |   /**

Added src/com/google/common/collect/ImmutableCollection.java (line 185)

       180 |     if (other.length < size) {
       181 |       other = ObjectArrays.newArray(other, size);
       182 |     } else if (other.length > size) {
       183 |       other[size] = null;
       184 |     }
>>>    185 |     copyIntoArray(other, 0);
       186 |     return other;
       187 |   }
       188 | 
       189 |   @Override
       190 |   public abstract boolean contains(@Nullable Object object);

Added src/com/google/common/collect/ImmutableCollection.java (line 457)

       452 |       return this;
       453 |     }
       454 | 
       455 |     @Override
       456 |     public Builder<E> add(E... elements) {
>>>    457 |       checkElementsNotNull(elements);
       458 |       ensureCapacity(size + elements.length);
       459 |       System.arraycopy(elements, 0, contents, size, elements.length);
       460 |       size += elements.length;
       461 |       return this;
       462 |     }

Added src/com/google/common/collect/ImmutableList.java (line 278)

       273 |       case 0:
       274 |         return ImmutableList.of();
       275 |       case 1:
       276 |         return new SingletonImmutableList<E>(elements[0]);
       277 |       default:
>>>    278 |         return new RegularImmutableList<E>(checkElementsNotNull(elements.clone()));
       279 |     }
       280 |   }
       281 | 
       282 |   /**
       283 |    * Views the array as an immutable list.  Checks for nulls; does not copy.

Added src/com/google/common/collect/ImmutableMap.java (line 289)

       284 |             }
       285 |             Arrays.sort(entries, 0, size,
       286 |                 Ordering.from(valueComparator).onResultOf(Maps.<V>valueFunction()));
       287 |           }
       288 |           entriesUsed = size == entries.length;
>>>    289 |           return RegularImmutableMap.fromEntryArray(size, entries);
       290 |       }
       291 |     }
       292 |   }
       293 | 
       294 |   /**

Added src/com/google/common/collect/ImmutableMapEntrySet.java (line 57)

        52 |       return asList().iterator();
        53 |     }
        54 | 
        55 |     @Override
        56 |     ImmutableList<Entry<K, V>> createAsList() {
>>>     57 |       return new RegularImmutableAsList<Entry<K, V>>(this, entries);
        58 |     }
        59 |   }
        60 | 
        61 |   ImmutableMapEntrySet() {}
        62 | 

Added src/com/google/common/collect/ImmutableSet.java (line 304)

       299 |       case 0:
       300 |         return of();
       301 |       case 1:
       302 |         return of(elements[0]);
       303 |       default:
>>>    304 |         return construct(elements.length, elements.clone());
       305 |     }
       306 |   }
       307 | 
       308 |   @SuppressWarnings("rawtypes") // necessary to compile against Java 8
       309 |   private static ImmutableSet copyOfEnumSet(EnumSet enumSet) {

Added src/com/google/common/collect/ImmutableSortedMap.java (line 107)

       102 |         ImmutableList.of(v1));
       103 |   }
       104 | 
       105 |   private static <K extends Comparable<? super K>, V> ImmutableSortedMap<K, V> ofEntries(
       106 |       ImmutableMapEntry<K, V>... entries) {
>>>    107 |     return fromEntries(Ordering.natural(), false, entries, entries.length);
       108 |   }
       109 | 
       110 |   /**
       111 |    * Returns an immutable sorted map containing the given entries, sorted by the
       112 |    * natural ordering of their keys.

Added src/com/google/common/collect/ImmutableSortedMap.java (line 500)

       495 |         case 0:
       496 |           return emptyMap(comparator);
       497 |         case 1:
       498 |           return of(comparator, entries[0].getKey(), entries[0].getValue());
       499 |         default:
>>>    500 |           return fromEntries(comparator, false, entries, size);
       501 |       }
       502 |     }
       503 |   }
       504 | 
       505 |   private final transient RegularImmutableSortedSet<K> keySet;

Added src/com/google/common/collect/ImmutableSortedSet.java (line 375)

       370 |   static <E> ImmutableSortedSet<E> construct(
       371 |       Comparator<? super E> comparator, int n, E... contents) {
       372 |     if (n == 0) {
       373 |       return emptySet(comparator);
       374 |     }
>>>    375 |     checkElementsNotNull(contents, n);
       376 |     Arrays.sort(contents, 0, n, comparator);
       377 |     int uniques = 1;
       378 |     for (int i = 1; i < n; i++) {
       379 |       E cur = contents[i];
       380 |       E prev = contents[uniques - 1];

Added src/com/google/common/collect/ImmutableSortedSet.java (line 385)

       380 |       E prev = contents[uniques - 1];
       381 |       if (comparator.compare(cur, prev) != 0) {
       382 |         contents[uniques++] = cur;
       383 |       }
       384 |     }
>>>    385 |     Arrays.fill(contents, uniques, n, null);
       386 |     return new RegularImmutableSortedSet<E>(
       387 |         ImmutableList.<E>asImmutableList(contents, uniques), comparator);
       388 |   }
       389 | 
       390 |   /**

Added src/com/google/common/collect/ImmutableSortedSet.java (line 387)

       382 |         contents[uniques++] = cur;
       383 |       }
       384 |     }
       385 |     Arrays.fill(contents, uniques, n, null);
       386 |     return new RegularImmutableSortedSet<E>(
>>>    387 |         ImmutableList.<E>asImmutableList(contents, uniques), comparator);
       388 |   }
       389 | 
       390 |   /**
       391 |    * Returns a builder that creates immutable sorted sets with an explicit
       392 |    * comparator. If the comparator has a more general type than the set being
S2330 (java) on sonar-server - 0 issues removed, 63 issues added - new ruling file

Added src/main/java/org/sonar/server/ce/ws/ActivityAction.java (line 126)

(source file not found at this revision: src/main/java/org/sonar/server/ce/ws/ActivityAction.java)

Added src/main/java/org/sonar/server/ce/ws/ActivityAction.java (line 127)

(source file not found at this revision: src/main/java/org/sonar/server/ce/ws/ActivityAction.java)

Added src/main/java/org/sonar/server/ce/ws/ActivityAction.java (line 131)

(source file not found at this revision: src/main/java/org/sonar/server/ce/ws/ActivityAction.java)

Added src/main/java/org/sonar/server/component/index/ComponentIndex.java (line 84)

(source file not found at this revision: src/main/java/org/sonar/server/component/index/ComponentIndex.java)

Added src/main/java/org/sonar/server/component/ws/SuggestionsAction.java (line 129)

(source file not found at this revision: src/main/java/org/sonar/server/component/ws/SuggestionsAction.java)

Added src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java (line 84)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java)

Added src/main/java/org/sonar/server/es/StickyFacetBuilder.java (line 105)

(source file not found at this revision: src/main/java/org/sonar/server/es/StickyFacetBuilder.java)

Added src/main/java/org/sonar/server/es/request/ProxyClearCacheRequestBuilder.java (line 72)

(source file not found at this revision: src/main/java/org/sonar/server/es/request/ProxyClearCacheRequestBuilder.java)

Added src/main/java/org/sonar/server/es/request/ProxyClearCacheRequestBuilder.java (line 76)

(source file not found at this revision: src/main/java/org/sonar/server/es/request/ProxyClearCacheRequestBuilder.java)

Added src/main/java/org/sonar/server/es/request/ProxyClusterHealthRequestBuilder.java (line 72)

(source file not found at this revision: src/main/java/org/sonar/server/es/request/ProxyClusterHealthRequestBuilder.java)

Added src/main/java/org/sonar/server/es/request/ProxyClusterStateRequestBuilder.java (line 72)

(source file not found at this revision: src/main/java/org/sonar/server/es/request/ProxyClusterStateRequestBuilder.java)

Added src/main/java/org/sonar/server/es/request/ProxyClusterStatsRequestBuilder.java (line 72)

(source file not found at this revision: src/main/java/org/sonar/server/es/request/ProxyClusterStatsRequestBuilder.java)

Added src/main/java/org/sonar/server/es/request/ProxyFlushRequestBuilder.java (line 72)

(source file not found at this revision: src/main/java/org/sonar/server/es/request/ProxyFlushRequestBuilder.java)

Added src/main/java/org/sonar/server/es/request/ProxyIndicesExistsRequestBuilder.java (line 72)

(source file not found at this revision: src/main/java/org/sonar/server/es/request/ProxyIndicesExistsRequestBuilder.java)

Added src/main/java/org/sonar/server/es/request/ProxyIndicesStatsRequestBuilder.java (line 72)

(source file not found at this revision: src/main/java/org/sonar/server/es/request/ProxyIndicesStatsRequestBuilder.java)

romainbrenguier and others added 2 commits August 4, 2026 11:05
Detect array covariance where an array of a derived type is assigned to
a variable declared as an array of its base type, which can lead to
ArrayStoreException at runtime.
Cover varargs method invocations with covariant array arguments,
including multiple vararg arguments, mixed varargs with regular
parameters, and edge cases like no vararg arguments passed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@romainbrenguier
romainbrenguier force-pushed the new-rule/SONARJAVA-6706-S2330 branch from 594a768 to 2648f82 Compare August 4, 2026 09:06
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #5875

Please review and merge it into your branch.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #5875

Please review and merge it into your branch.

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #5880

Please review and merge it into your branch.

…n S2330

Handle LAMBDA_EXPRESSION nodes directly to detect covariant array returns
in expression-bodied lambdas (e.g. `() -> new Apple[1]`), which were
previously missed since they have no RETURN_STATEMENT node.

For varargs, check the first argument against the whole array type first,
falling back to element-type check only if no issue was reported. This
prevents double-reporting when an array-of-arrays argument matches both
the vararg type and its element type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #5880

Please review and merge it into your branch.

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@gitar-bot

gitar-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 resolved / 2 findings

Implements new rule S2330 to detect unsafe array covariance leading to potential ArrayStoreException at runtime. Addressed expression-bodied lambda handling and varargs double-reporting findings.

✅ 2 resolved
Edge Case: Expression-bodied lambdas escape covariance detection

📄 java-checks/src/main/java/org/sonar/java/checks/ArrayCovarianceCheck.java:79-91
The return-path handling only fires on Tree.Kind.RETURN_STATEMENT, but an expression-bodied lambda such as Supplier<Fruit[]> s = () -> new Apple[1]; has no return statement node (its body is an ExpressionTree, not a block with a return). Such covariant lambda bodies are therefore never checked, a false negative compared to the block-lambda case that is tested at ArrayCovarianceCheckSample.java:85-89. Consider also handling LAMBDA_EXPRESSION nodes directly: when the body is an ExpressionTree, compare lambda.symbol().returnType().type() against the body expression's type.

Edge Case: First vararg argument is checked twice in visitInvocation

📄 java-checks/src/main/java/org/sonar/java/checks/ArrayCovarianceCheck.java:111-116
For the first vararg argument, visitInvocation both checks it against the whole vararg array type (line 112, treating the call as passing a pre-built array) and against the vararg element type (line 114 loop starts at nonVarargCount). When a covariant array-of-arrays is passed to an array-typed varargs parameter (e.g. Object[]... ), both checks can fire and report two issues on the same expression. Consider only running the whole-array check when a single vararg argument is supplied, and starting the element loop after it, to avoid redundant/double reporting.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

sonarqube-next Bot commented Aug 4, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant